/* $Id$ */

/*
	Copyright (c) 2011, Yury Y. Kuznetsov
	All rights reserved.

	Redistribution and use in source and binary forms, with or without modification, 
	are permitted provided that the following conditions are met:

	o Redistributions of source code must retain the above copyright notice, 
	  this list of conditions and the following disclaimer.
	o Redistributions in binary form must reproduce the above copyright notice, 
	  this list of conditions and the following disclaimer in the documentation 
	  and/or other materials provided with the distribution.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
	SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
	TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
	OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
	WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <iostream>
#include <vector>
#include <cassert>
#include <stdlib.h>

#include "cgetopt.h"
#include "profiler.h"

#ifdef _WIN32
#	include <Windows.h>
#else
#	include <pthread.h>
#endif

typedef double data_t;

void matrix_print(data_t *matr, size_t n_col, const char *title = 0);

bool verbose = false;
bool randomize = false;

void
usage(const char *me)
{
	std::cerr << "Usage: " << me << " [-h][-v][-r][-n threads] n-rows"<< std::endl;
	exit(EXIT_FAILURE);
}

void
matrix_generate(data_t *matr, size_t n_col)
{
	size_t nn = n_col * n_col;
	for(size_t i = 0 ; i < nn ; ++i)
		*matr++ = static_cast<data_t>(i);
}

void
matrix_generate_random(data_t *matr, size_t n_col)
{
	size_t nn = n_col * n_col;
	for(size_t i = 0 ; i < nn ; ++i)
		*matr++ = static_cast<data_t>(rand());
}

void
matrix_zero(data_t *matr, size_t n_col)
{
	size_t nn = n_col * n_col;
	for(size_t i = 0 ; i < nn ; ++i)
		*matr++ = static_cast<data_t>(0);
}

void
matrix_print(data_t *matr, size_t n_col, const char *title)
{
	std::cerr << "----------------" << std::endl;
	for(size_t i = 0 ; i < n_col ; ++i){
		for(size_t j = 0 ; j < n_col ; ++j)
			std::cerr << *matr++ << " " ;
		std::cerr << std::endl;
	}
}

struct dot_product_params {
	int order;
	int step;
	size_t cols;
	data_t const *src;
	data_t *dst;

	dot_product_params(int ord, int step, size_t cols, data_t const *src, data_t *dst) 
		: order(ord), step(step), cols(cols), src(src), dst(dst) {}
};


#ifdef _WIN32
typedef HANDLE mutex_t;
typedef HANDLE thread_t;
typedef LPTHREAD_START_ROUTINE thread_entry_t;
#else
typedef pthread_t thread_t;
typedef pthread_mutex_t mutex_t;
typedef void * (* thread_entry_t)(void *);
#endif

thread_t
thread_create(thread_entry_t entry, void *arg)
{
#ifdef _WIN32
	return CreateThread(0, 0, entry, arg, 0, 0);
#else
	pthread_t tid;
	int rc = pthread_create(&tid, 0, entry, arg);
	assert(rc == 0);
	return tid;
#endif
}

bool
thread_create_mutex(mutex_t *mutex)
{
	if(mutex == NULL)
		return false;
#ifdef _WIN32
	*mutex = CreateMutex( 
		NULL,              // default security attributes
		FALSE,             // initially not owned
		NULL);             // unnamed mutex
	return *mutex != NULL;
#else
	return pthread_mutex_init(mutex, 0) == 0;
#endif
}

int
thread_join(thread_t handle)
{
#ifdef _WIN32
	DWORD code;

	if(WaitForSingleObject(handle, INFINITE) == WAIT_OBJECT_0) {
		GetExitCodeThread(handle, &code);
		CloseHandle(handle);
	}

	return code;
#else
	void *code;
	pthread_join(handle, &code);

	return static_cast<int>((size_t)code);
#endif
}

bool
thread_mutex_lock(mutex_t *mutex)
{
#ifdef _WIN32
	return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0;
#else
	return pthread_mutex_lock(mutex) == 0;
#endif
}

bool
thread_mutex_unlock(mutex_t *mutex)
{
#ifdef _WIN32 
	return mutex != NULL && ReleaseMutex(*mutex);
#else
	return pthread_mutex_unlock(mutex) == 0;
#endif
}

mutex_t console_lock;

#ifdef _WIN32
DWORD WINAPI
#else
void *
#endif
dot_product(void *arg)
{
	dot_product_params *ord = reinterpret_cast<dot_product_params *>(arg);

	size_t n_col = ord->cols;
	data_t const *col_p, *col_p_end = ord->src + n_col, 
		*row_p, *row_p_end = ord->src + n_col * n_col;

	for(int i = ord->order ; i < ord->cols ; i += ord->step) {
		if(verbose) {
			thread_mutex_lock(&console_lock);
			std::cout << "dot_product(" << ord->order << ") col = " << i << std::endl;
			thread_mutex_unlock(&console_lock);
		}
		data_t *rp = ord->dst + i * n_col;
		row_p = ord->src + i * n_col;
		data_t const *inn_p1_end = row_p + n_col;
		for(col_p = ord->src ; col_p != col_p_end ; col_p += 1) {
			data_t const *inn_p1 = row_p, *inn_p2 = col_p; 
			data_t s = 0;
			while(inn_p1 != inn_p1_end) {
				s += *inn_p1 * *inn_p2;
				++inn_p1;
				inn_p2 += n_col;
			}
			*rp++ = s;
		}
	}
	return 0;
}

int
main(int argc, char **argv)
{
	Getopt g;
	int c;
	const char *me = argv[0];
	int n_threads = 1;

	while((c = g.getopt(argc, argv, "hvn:r")) != -1) switch(c){
	case 'v':
		verbose = true;
		break;
	case 'r':
		randomize = true;
		break;
	case 'n':
		n_threads = atoi(g.optarg);
		break;
	case 'h':
	case '?':
	default:
		usage(me);
	}
	argc -= g.optind;
	argv += g.optind;

	if(argc != 1)
		usage(me);
	
	data_t *matr0, *matr1;
	size_t n_col;

	n_col = atoi(argv[0]);

	std::cout << "n_col == " << n_col << ", n_threads == " << n_threads << std::endl;

	matr0 = new data_t[n_col * n_col];
	matr1 = new data_t[n_col * n_col];

	if(randomize)
		matrix_generate_random(matr0, n_col);
	else
		matrix_generate(matr0, n_col);

	if(verbose)
		matrix_print(matr0, n_col, "Matrix");

	yyk::Profiler p;

	p.start();
	typedef std::vector<std::pair<thread_t, dot_product_params *> > thvector;

	thvector drones;
	drones.reserve(n_threads);

	thread_create_mutex(&console_lock);

	for (int i = 0 ; i < n_threads ; ++i) {
		dot_product_params *dp;
		thread_t handle = thread_create(dot_product, dp = new dot_product_params(i, n_threads, n_col, matr0, matr1));
		drones.push_back(thvector::value_type(handle, dp));
	}

	// wait for drones' completion
	for (thvector::iterator i = drones.begin() ; i != drones.end() ; ++i) {
		thread_join(i->first);
		delete i->second;
	}
	p.stop();

	if(verbose)
		matrix_print(matr1, n_col, "Result");
	std::cout << "CHK: matr[" << n_col -1 << "," 
		<< n_col -1 << "] = " << matr1[n_col*n_col - 1] << std::endl;
	std::cout << "Elapsed: " << p.elapsed() / 1000.0 << " sec." << std::endl;

	delete [] matr1; matr1 = 0;
	delete [] matr0; matr0 = 0;

	return 0;
}

// Local Variables:
// mode: c++
// c-basic-offset: 4
// tab-width: 4
// End:
